Sicily Sailing GPS track summary

Author

jsp

Published

October 25, 2024

Track loading and formatting

The xml file with the navionics tracks is parsed.

  • Coordinates, time and speed are extracted.
  • Speed is converted from m/s to knots
  • COG is calculated as consecutive bearings from a thinned 10min dataset
  • Distance is calculated for generated for the total distance and cumulative per day
Code
# temp <- tempfile(fileext = ".zip")
# download.file("https://drive.google.com/drive/folders/1FlAM74hAPtFC3qIcZF5nQ03UK-WP3hZc&export=download",
#   temp)
# out <- unzip(temp, exdir = tempdir())
# gpx_parsed <- htmlTreeParse(file = out[14], useInternalNodes = TRUE)

gpx_parsed <- htmlTreeParse(file = "Sicily2021Tracks", useInternalNodes = TRUE)

coords <- xpathSApply(doc = gpx_parsed, path = "//trkpt", fun = xmlAttrs)
elevation <- xpathSApply(doc = gpx_parsed, path = "//trkpt/ele", fun = xmlValue)
date_time <- xpathSApply(doc = gpx_parsed, path = "//trkpt/time", fun = xmlValue)
speed <- xpathSApply(doc = gpx_parsed, path = "//trkpt/extensions/navionics_speed", fun = xmlValue)


df <- data.frame(
  lat = as.numeric(coords["lat", ]),
  lon = as.numeric(coords["lon", ]),
  elevation = as.numeric(elevation),
  date = lubridate::as_date(date_time),
  time = lubridate::parse_date_time(date_time, "Y-m-d*H:M:S"),
  kts = as.numeric(speed) * 1.944) |> # convert from m/s to knots 
  mutate(
    dist_segment = track_distance(lat, lon) / 1852, # convert from meters to nm
  )
# outlier check
outlier <- which(df$dist_segment > .5)
outlier_area <- c(outlier, outlier-1) |> sort()

df_1min <- df |>
  dplyr::filter(second(time) < 1) |>
  mutate(cog = track_bearing(lat, lon),
         cog = ifelse(cog < 0, cog + 360, cog)
  ) |>
  mutate(
    dist_segment = track_distance(lat, lon) / 1852, # convert from meters to nm
  )
df_1min$dist_segment[1] <- 0
df_1min <- df_1min |>
  mutate(dist = cumsum(dist_segment) |> round(digits = 1)) |>
  group_by(date) |>
  mutate(dist_day = cumsum(dist_segment) |> round(digits = 1)) |> 
  ungroup()

df_10min <- df_1min |>
  dplyr::filter(second(time) <1, minute(time) %% 10 == 0) |>
  mutate(cog = track_bearing(lat, lon),
         cog = ifelse(cog < 0, cog + 360, cog)) 
Code
head(df, 5)
       lat      lon elevation       date                time     kts
1 38.15578 14.77282        48 2021-10-03 2021-10-03 08:16:54 2.60496
2 38.15582 14.77245        44 2021-10-03 2021-10-03 08:16:55 4.49064
3 38.15591 14.77257        57 2021-10-03 2021-10-03 08:16:55 4.49064
4 38.15587 14.77253        46 2021-10-03 2021-10-03 08:16:56 1.94400
5 38.15592 14.77252        50 2021-10-03 2021-10-03 08:16:58 2.43000
  dist_segment
1           NA
2  0.022317834
3  0.008898046
4  0.003125085
5  0.002768580
Code
tail(df, 5)
            lat     lon elevation       date                time kts
230251 38.15554 14.7731        44 2021-10-08 2021-10-08 15:24:03   0
230252 38.15554 14.7731        44 2021-10-08 2021-10-08 15:24:04   0
230253 38.15554 14.7731        45 2021-10-08 2021-10-08 15:24:05   0
230254 38.15554 14.7731        44 2021-10-08 2021-10-08 15:24:06   0
230255 38.15554 14.7731        44 2021-10-08 2021-10-08 15:24:07   0
       dist_segment
230251 2.400859e-04
230252 5.974434e-05
230253 1.194887e-04
230254 5.974434e-05
230255 0.000000e+00

Extract full hour data.

Code
df_1hour <- df_10min |> mutate(hour = hour(time)) |>
  group_by(date, hour) |>
  slice(1) |>
  ungroup() |> arrange(time)
  #dplyr::filter(minute(time) == 0, second(time) < 1) 

Overview plot

Blue markers are the positions at the full hour for approximate logbook entries. Red markers show beginning and end of missing segments.

Code
leaflet() |>
  addTiles() |>
  addPolylines(data = df_1min, lat = ~lat, lng = ~lon, 
               color = "#000000", opacity = 0.8, weight = 3) |>
  leaflet::addAwesomeMarkers(
    data = df_1hour, lat = ~lat, lng = ~lon, label = ~as.character(hour(time))) |>
  leaflet::addAwesomeMarkers(data = df[outlier_area, ], lat = ~lat, lng = ~lon,
                             label = ~as.character(hour(time)), 
                             icon = awesomeIcons(icon = 'ios-close', 
                                                 iconColor = 'black',
                                                 library = 'ion',
                                                 markerColor = 'red')) 
Code
leaflet() |>
  addTiles() |>
  addPolylines(data = df, lat = ~lat, lng = ~lon, 
               color = "#000000", opacity = 0.8, weight = 3) |>
  leaflet::addAwesomeMarkers(
    data = df_1hour, lat = ~lat, lng = ~lon, label = ~as.character(hour)) |>
  leaflet::addAwesomeMarkers(
    data = df[outlier_area, ], lat = ~lat, lng = ~lon,
    label = ~as.character(hour(time)), 
    icon = awesomeIcons(icon = 'ios-close', iconColor = 'black',
                        library = 'ion', markerColor = 'red'))

Hourly log book

Code
df_1hour |>
  mutate(latitude = deg2dms(lat, type = "cat"),
         longitude = deg2dms(lon, type = "cat"),
         # add distance
         across(c(cog, kts, dist), \(x) round(x, 1))
         ) |>
  mutate(time = sprintf("%02d:%02d", hour(time), minute(time)),
         date = str_remove(as.character(date), "^[0-9]*-"))|>
  select(date, time, latitude, longitude, cog, kts, dist_day, dist) |>
  gt::gt() |>
  gt::opt_interactive(use_text_wrapping = FALSE) |>
  gt::cols_width(dplyr::any_of(c("latitude", "longitude")) ~ gt::px(150))

COG

Code
df_10min |>
  ggplot(aes(x = time, y = cog)) + geom_point() + geom_line()

Code
df_1min |>
  ggplot(aes(x = time, y = cog)) + geom_point() + geom_line()

Distance

Checking Cumulative Miles in total and per day. The full track shows missing segments, marked in red in the full data route above.

Code
df_10min |>
  ggplot(aes(x = time, y = dist)) + geom_point() + geom_line()

Code
df_10min |>
  ggplot(aes(x = time, y = dist_day)) + geom_point() + geom_line()

missing segments

Code
df |>
  ggplot(aes(x = time, y = dist_segment)) + geom_point() + geom_line()